package com.epam.cdp.selenium2.core.runner;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.testng.IInvokedMethodListener;
import org.testng.TestListenerAdapter;
import org.testng.TestNG;
import org.testng.xml.Parser;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import org.xml.sax.SAXException;

import com.epam.cdp.selenium2.core.configuration.GlobalConfiguration;
import com.epam.cdp.selenium2.core.configuration.SeleniumInstanceConfiguration;
import com.epam.cdp.selenium2.core.results.logger.Logger;
import com.epam.cdp.selenium2.core.runner.cli.CliParser;
import com.epam.cdp.selenium2.core.runner.cli.SeleniumConfigCliHandler;
import com.epam.cdp.selenium2.core.runner.options.OptionsParser;
import com.epam.cdp.selenium2.core.ui.Browser;

/**
 * Common project class.
 * 
 * @author yyudzitski
 */
public class TestRunner
{
  private String[] cmdArgs;

  private static TestNG testNg = new TestNG();

  CliParser cli = new CliParser();

  @SuppressWarnings( "rawtypes" )
  List<Class> listeners;

  /**
   * Constructor. Default listener {@link TestListener} will be used.
   * 
   * @param args
   */
  @SuppressWarnings( "rawtypes" )
  public TestRunner( String[] args )
  {
    cmdArgs = args;
    listeners = new ArrayList<Class>();
    addListener( TestListener.class );
  }

  /**
   * Constructor. User listener will be used.
   * 
   * @param args
   * @param listener - custom test listener witch must be extended from {@link TestListenerAdapter} and implement
   *          {@link IInvokedMethodListener}
   */
  @SuppressWarnings( "rawtypes" )
  public TestRunner( String[] args, Class listener )
  {
    cmdArgs = args;
    listeners = new ArrayList<Class>();
    addListener( listener );
  }

  /**
   * Add custom test listener witch must be extended from {@link TestListenerAdapter} and implement
   * {@link IInvokedMethodListener}. Must be called before run() method. called.
   * 
   * @param listener
   */
  @SuppressWarnings( "rawtypes" )
  public void addListener( Class listener )
  {
    listeners.add( listener );
  }

  /**
   * Fill configuration with CLI parameters.
   */
  private void initializeConfiguration()
  {
    /** Fill default SeleniumRC configuration */
    GlobalConfiguration.getInstance().getSeleniumConfiguration().getDefaultConfiguration()
      .setSeleniumHost( cli.seleniumHostName );
    GlobalConfiguration.getInstance().getSeleniumConfiguration().getDefaultConfiguration()
      .setSeleniumPort( cli.seleniumHostPort );
    GlobalConfiguration.getInstance().getSeleniumConfiguration().getDefaultConfiguration()
      .setHostLogin( cli.seleniumHostLogin );
    GlobalConfiguration.getInstance().getSeleniumConfiguration().getDefaultConfiguration()
      .setHostPassword( cli.seleniumHostPassword );
    GlobalConfiguration.getInstance().getSeleniumConfiguration().getDefaultConfiguration()
      .setDriverType( cli.driverType );
    /** Fill additional SeleniumRC configurations */
    GlobalConfiguration.getInstance().getSeleniumConfiguration().setAdditionalConfigurations( cli.configs );
    /** Fill root URL */
    GlobalConfiguration.getInstance().setRootUrl( cli.rootUrl );
    /** Fill TestNG configuration */
    GlobalConfiguration.getInstance().getTestngConfiguration().setTestngFiles( cli.xmlSuite );
    /** Fill Options configuration */
    try
    {
      SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
      parser.parse( cli.optionsFile, new OptionsParser() );
    }
    catch( SAXException saxExcept )
    {
      saxExcept.printStackTrace();
    }
    catch( ParserConfigurationException parseExcept )
    {
      parseExcept.printStackTrace();
    }
    catch( IOException ioExcept )
    {
      Logger.warn( "File with user's options not found: " + new File( cli.optionsFile ).getAbsolutePath() );
    }
  }

  /**
   * Parse command line arguments and run tests.
   */
  public void run()
  {
    /** Parse command line arguments */
    CmdLineParser.registerHandler( SeleniumInstanceConfiguration.class, SeleniumConfigCliHandler.class );
    CmdLineParser parser = new CmdLineParser( cli );
    try
    {
      parser.parseArgument( cmdArgs );
    }
    catch( CmdLineException e )
    {
      e.printStackTrace();
    }
    initializeConfiguration();
    /** Parse TestNG XML files and run tests */
    String currentSuiteName = null;

    try
    {
      List<String> includedGroups = GlobalConfiguration.getInstance().getTestngConfiguration().getIncludedGroups();
      List<String> excludedGroups = GlobalConfiguration.getInstance().getTestngConfiguration().getExcludedGroups();
      for( String suite : GlobalConfiguration.getInstance().getTestngConfiguration().getTestngFiles() )
      {
        currentSuiteName = suite;
        try
        {
          for( XmlSuite xmlSuite : new Parser( suite ).parseToList() )
          {
            for( XmlTest test : xmlSuite.getTests() )
            {
              test.setIncludedGroups( includedGroups );
              test.setExcludedGroups( excludedGroups );
            }
            testNg.setCommandLineSuite( xmlSuite );
          }
        }
        catch( IOException ioExcept )
        {
          Logger.error( "Suite not found: " + new File( currentSuiteName ).getAbsolutePath() );
        }
      }
      testNg.setListenerClasses( listeners );
      testNg.run();
    }
    catch( Exception ex )
    {
      Logger.fatal( "Fatal error during running test suites." );
      throw new RuntimeException( ex );
    }
    finally
    {
      Browser.terminate();
    }
  }

  /**
   * Remove after implementation will be finished.
   * 
   * @param args
   */
  public static void main( String[] args )
  {
    new TestRunner( args ).run();
  }

}